#   Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
from parl.env.atari_wrappers import FrameStack
from parl.env.mujoco_wrappers import wrap_rms, VecNormalizeEnv

TEST_EPISODE = 3
# wrapper parameters for atari env
OBS_FORMAT = 'NCHW'
# wrapper parameters for mujoco env
GAMMA = 0.9


class ParallelEnv(object):
    def __init__(self, env):
        self.env_list = [LocalEnv(env)]
        self._max_episode_steps = self.env_list[0]._max_episode_steps

        self.total_steps = 0
        self.episode_steps_list = [0]
        self.episode_reward_list = [0]
        # used for env initialization for evaluating in mujoco environment
        self.eval_ob_rms = None

    def reset(self):
        obs_list = [env.reset() for env in self.env_list]
        self.obs_list = np.array(obs_list)
        return self.obs_list

    def step(self, action_list):
        next_obs_list, reward_list, done_list, info_list = [], [], [], []
        for i in range(1):
            self.total_steps += 1
            next_obs, reward, done, info = self.env_list[i].step(
                action_list[i])
            self.episode_steps_list[i] = 1
            self.episode_reward_list[i] = reward
            # if done or self.episode_steps_list[i] >= self._max_episode_steps:
            #     next_obs = self.env_list[i].reset()
            #     self.episode_steps_list[i] = 0
            #     self.episode_reward_list[i] = 0
            next_obs_list.append(next_obs)
            reward_list.append(reward)
            done_list.append(done)
            info_list.append(info)
        return np.array(next_obs_list), np.array(reward_list), np.array(
            done_list), np.array(info_list)


def wrap_deepmind(env,
                  framestack=True,
                  test=False,
                  obs_format='NHWC'
                  ):
    """Configure environment for DeepMind-style Atari.

    Args:
        dim (int): Dimension to resize observations to (dim x dim).
        framestack (bool): Whether to framestack observations.
        obs_format (str): observation output format
        test (bool): whether this is a test env
        test_episodes (int): when test, number of episodes for each evaluation
    """
    env = VecNormalizeEnv(env, gamma=0)
    if test:
        env.eval()
    if framestack:
        env = FrameStack(env, 4, obs_format)
    return env


class LocalEnv(object):
    def __init__(self, env, env_seed=None, test=False, ob_rms=None):
        self._max_episode_steps = env._max_episode_steps
        # is instance of gym.spaces.Box
        if hasattr(env.action_space, 'high'):
            self.continuous_action = True
            if test:
                self.env = wrap_rms(env, GAMMA, test=True, ob_rms=ob_rms)
            else:
                self.env = wrap_rms(env, gamma=GAMMA)
        # is instance of gym.spaces.Discrete
        elif hasattr(env.action_space, 'n'):
            self.continuous_action = False
            if test:
                self.env = wrap_deepmind(
                    env,
                    test=True,
                    obs_format=OBS_FORMAT)

            else:
                self.env = wrap_deepmind(
                    env, obs_format=OBS_FORMAT)
        else:
            raise AssertionError(
                'act_space must be instance of gym.spaces.Box or gym.spaces.Discrete'
            )

        self.obs_space = self.env.observation_space
        self.act_space = self.env.action_space

        if env_seed:
            self.env.seed(env_seed)

    def reset(self):
        return self.env.reset()

    def step(self, action):
        return self.env.step(action)

    def render(self):
        return self.env.render()
